#include "work_thread.h"
#include <stdbool.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <openssl/md5.h>
#include <mysql/mysql.h>

#define  MD5_LEN  16 
#define  BUFF_SIZE 1024*16

#define ARGC 10

#define ERR "err"
#define PIPE_ERR "err pipe"
#define FORK_ERR "err pipe"
#define NO_FILENAME "err nofilename"
#define FILE_OPEN_ERR "err file open"
#define UNDONE ".tmp"
#define SQL_ERR "err sql"
#define FILE_EXIST "exist"

char* get_cmd(char buff[], char* myargv[])
{
    if (buff == NULL || myargv == NULL)
    {
        return NULL;
    }

    int i = 0;
    char* ptr = NULL;
    char* s = strtok_r(buff, " ", &ptr);
    while (s != NULL)
    {
        myargv[i++] = s;
        s = strtok_r(NULL, " ", &ptr);
    }
    return myargv[0];
}

void run_cmd(int c, char* cmd, char* myargv[])
{
    if (cmd == NULL || myargv == NULL)
    {
        return;
    }

    int pipefd[2];
    if (pipe(pipefd) == -1)
    {
        send(c, PIPE_ERR, strlen(PIPE_ERR), 0);
        return;
    }

    pid_t pid = fork();
    if (pid == -1)
    {
        send(c, ERR, strlen(ERR), 0);
        return;
    }
    if (pid == 0)
    {
        close(pipefd[0]);//关读
        //重定向stdout stderr
        dup2(pipefd[1], 1);
        dup2(pipefd[1], 2);
        execvp(cmd, myargv);
        printf("cmd:%s not found\n", cmd);
        exit(0);
    }
    close(pipefd[1]);

    char read_pipe[4096] = {"ok#"};
    wait(NULL);
    read(pipefd[0], read_pipe + 3, 4092);
    send(c, read_pipe, strlen(read_pipe), 0);
}

void fun_md5(int fd, unsigned char* md)
{
    MD5_CTX ctx;
    MD5_Init(&ctx);

    unsigned long len = 0;
    char buff[ BUFF_SIZE ];
    while( (len = read(fd,buff,BUFF_SIZE )) > 0 )
    {
        MD5_Update(&ctx,buff,len);
    }

    MD5_Final(md,&ctx);
}

void md5_to_char(unsigned char md[], char *md5)
{
    for(int i = 0; i < MD5_LEN; i++ )
    {
        sprintf(md5 + i * 2, "%02x", md[i]);
    }
}

void print_md5(unsigned char md[])
{
    int i = 0;
    for( ; i < MD5_LEN; i++ )
    {
        printf("%02x",md[i]);
    }

    printf("\n");
}

MYSQL_RES* dosql(const char *sql)
{
    if (sql == NULL)
    {
        return NULL;
    }

    MYSQL mysql_con;
    MYSQL* mysql = mysql_init(&mysql_con);
    if (mysql == NULL) {
        printf("mysql init err\n");
        return NULL;
        //exit(1);
    }
    
    mysql = mysql_real_connect(mysql, "localhost", "root", "20020829", "c2022", 3306, NULL, 0);
    if (mysql == NULL) {
        printf("connect mysql failed\n");
        return NULL;
        //exit(1);
    }

    //char* sql = "insert into student value(1006, '王刚', 25)";
    //char* sql = "update student set id=1005 where name='王刚'";
    //char* sql = "delete student from student where name='王刚'";
    //char* sql = "select * from student";
    int query_res = mysql_query(mysql, sql);//返回值代表查询成功或失败
    if (query_res != 0) {
        printf("query err: %s\n", mysql_error(mysql));
        mysql_close(mysql);
        return NULL;
        //exit(1);
    }

    //获取结果集 mysql_store_result();
    MYSQL_RES* mysql_res = mysql_store_result(mysql);//获取的结果集 指向堆区内存
    if (mysql_res == NULL) {
        //printf("res err:%s\n", mysql_error(mysql));
        mysql_close(mysql);
        return NULL;
        //exit(1);
    }

    //返回结果集
    mysql_close(mysql);
    return mysql_res;
}

bool isExist(const char* md5, char* srcfilename)
{
    char sql[512] = {0};
    snprintf(sql, 512, "select filename, val_md5 from md5 where val_md5='%s';", md5);
    MYSQL_RES* mysql_res = dosql(sql);
    if (mysql_res == NULL)
    {
        return false;
    }

    //获取结果集中有多少行数据 mysql_num_rows();
    int num = mysql_num_rows(mysql_res);
    if (num == 0) {
        printf("没有记录\n");
        mysql_free_result(mysql_res);
        return false;
        //exit(1);
    }
    printf("查询到%d条记录\n", num);

    //提取每一行（一个记录）mysql_fetch_row();
    for (int i = 0; i < num; ++i) {
        MYSQL_ROW sqlrow = mysql_fetch_row(mysql_res);
        printf("%s  %s\n", sqlrow[0], sqlrow[1]);
        strcpy(srcfilename, sqlrow[0]);
    }

    //释放结果集占用空间 mysql_free_result();
    mysql_free_result(mysql_res);
    return true;
}

bool saveMD5_in_Sql(const char* filename, const char* md5){

        //unsigned char md[MD5_LEN] = {0};
        //fun_md5(fd, md);
        //char md5[MD5_LEN * 2 + 1] = {0};
        //md5_to_char(md, md5);

        //char* sql = "insert into student value(1006, '王刚', 25)";
        char sql[512] = {0};
        snprintf(sql, 512, "insert into md5(filename, val_md5) value('%s', '%s');", filename, md5);
        dosql(sql);
        return true;
        //mysql_free_result(dosql(sql));
}

void send_file(int c, char *filename)
{
    if (filename == NULL)
    {
        send(c, NO_FILENAME, strlen(NO_FILENAME), 0);
        return;
    }

    int fd = open(filename, O_RDONLY);
    if (fd == -1)
    {
        send(c, FILE_OPEN_ERR, strlen(FILE_OPEN_ERR), 0);
        return;
    }

    //ok#size
    long long filesize = lseek(fd, 0, SEEK_END);
    if (filesize == -1)
    {
        send(c, FILE_OPEN_ERR, strlen(FILE_OPEN_ERR), 0);
        return;
    }

    //计算md值
    lseek(fd, 0, SEEK_SET);
    unsigned char md[MD5_LEN + 1] = {0};
    fun_md5(fd, md);
    print_md5(md);

    char status_buff[128] = "ok#";
    sprintf(status_buff + 3, "%lld", filesize);
    strcat(status_buff, " ");
    md5_to_char(md, status_buff + strlen(status_buff));
    send(c, status_buff, strlen(status_buff), 0);

    memset(status_buff, 0, 128);
    if (recv(c, status_buff, 127, 0) <= 0)
    {
        return;
    }

    if (strncmp("ok#", status_buff, 3) != 0)
    {
        //客户端放弃下载
        return;
    }

    int offset = atoi(status_buff + 3);//断点续传，获取文件偏移
    lseek(fd, offset, SEEK_SET);

    char data[1024] = {0};
    int num = 0;
    long long curr_size = 0;
    while ((num = read(fd, data, 1024)) > 0)
    {
        curr_size += num;
        if (send(c, data, num, 0) <= 0)
        {
            break;
        }
    }
    //printf("%lld\n", curr_size);

    close(fd);
}

void recv_file(int c, char *myargv[])
{
    //                    filename             filesize             md5
    if (myargv == NULL || myargv[1] == NULL || myargv[2] == NULL || myargv[3] == NULL)
    {
        send(c, NO_FILENAME, strlen(NO_FILENAME), 0);
        return;
    }

    //print_md5(myargv[3]);
    printf("%s\n", myargv[3]);

    //秒传 判断文件是否存在
    char srcfilename[128] = {0};
    if (isExist(myargv[3], srcfilename))
    {
        printf("文件传输成功！\n");

        //执行文件链接指令 创建链接文件
        if (strncmp(srcfilename, myargv[1], strlen(myargv[1])) != 0)
        {
            pid_t pid = fork();
            if (pid == -1)
            {
                exit(1);
            }

            char cmd[256] = {0};
            snprintf(cmd, 256, "ln %s %s", srcfilename, myargv[1]);
            char *tmpargv[10] = {0};
            char *index = NULL;
            tmpargv[0] = strtok_r(cmd, " ", &index);
            int i = 0;
            while (tmpargv[i] != 0)
            {
                tmpargv[++i] = strtok_r(NULL, " ", &index);
            }
            if (pid == 0)
            {
                execvp(tmpargv[0], tmpargv);
                exit(1);
            }
        }

        send(c, FILE_EXIST, strlen(FILE_EXIST), 0);
        //close(c);
        return;
    }

    //为文件名拼接 .tmp --断点续传
    char filename[128] = {0};
    strcpy(filename, myargv[1]);
    strcat(filename, UNDONE);

    //打开(创建目标文件)，获取文件偏移
    int fd = open(filename, O_WRONLY);
    int offset = 0;
    if (fd == -1)
    {
        fd = open(filename, O_WRONLY | O_CREAT, 0600);
        if (fd == -1)
        {
            send(c, "err", 3, 0);
        }
    }
    else
    {
        offset = lseek(fd, 0, SEEK_END);//获取文件大小
    }

    //发送偏移
    char send_status[128] = {"ok#"};
    sprintf(send_status + 3, "%d", offset);//发送段插入偏移
    send(c, send_status, strlen(send_status), 0);

    //文件大小
    long long filesize = atoll(myargv[2]);

    long long curr_file = offset;
    int num = 0;
    char data[1024] = {0};
    bool tag = true;
    printf("%lld\n", filesize);
    while (true)
    {
        num = recv(c, data, 1024, 0);
        if (num <= 0)
        {
            //printf("ser close\n");
            break;
        }
        write(fd, data, num);
        curr_file += num;

        if (curr_file >= filesize)
        {
            break;
        }
    }
    printf("%lld\n", curr_file);

    //校验：重新打开文件，获取md5值
    close(fd);
    fd = open(filename, O_RDONLY);
    if (fd == -1)
    {
        send(c, FILE_OPEN_ERR, strlen(FILE_OPEN_ERR), 0);
    }
    lseek(fd, 0, SEEK_SET);

    //获取md5值
    unsigned char md[MD5_LEN + 1] = {0};
    fun_md5(fd, md);
    print_md5(md);
    char md5[MD5_LEN * 2 + 1] = {0};
    md5_to_char(md, md5);
    printf("%s\n", md5);
    if (strcmp(md5, myargv[3]) != 0)
    {
        send(c, ERR, strlen(ERR), 0);
        return;
    }

    //校验完成，返回
    send(c, "ok#", strlen("ok#"), 0);

    //对文件名进行处理
    pid_t pid = fork();
    if (pid == -1)
    {
        exit(1);
    }

    char cmd[128] = {"mv "};
    strcat(cmd, filename);
    strcat(cmd, " ");
    strcat(cmd, myargv[1]);
    char* tmpargv[10] = {0};
    char* index = NULL;
    tmpargv[0] = strtok_r(cmd, " ", &index);
    int i = 0;
    while (tmpargv[i] != 0)
    {
        tmpargv[++i] = strtok_r(NULL, " ", &index);
    }
    if (pid == 0)
    {
        execvp(tmpargv[0], tmpargv);
        exit(1);
    }
    saveMD5_in_Sql(myargv[1], md5);
    close(fd);
}

void *work_thread(void *arg)
{
    int c = (int)arg;

    while (true)
    {
        //解析报文
        char buff[128] = {0};
        //解析报文  get filename,up filename, rm filename
        int n = recv(c, buff, 127, 0);
        if (n <= 0)
        {
            printf("close: (%d)\n", c);
            break;
        }
        if (strncmp("exit", buff, 4) == 0)
        {
            break;
        }
        printf("%s\n", buff);

        char* myargv[ARGC] = {0};
        char* cmd = get_cmd(buff, myargv);
        if (cmd == NULL)
        {
            send(c, ERR, strlen(ERR), 0);
            continue;
        }
        
        if (strcmp(cmd, "get") == 0)
        {
            //下载
            send_file(c, myargv[1]);
        }
        else if (strcmp(cmd, "up") == 0)
        {
            //上传
            recv_file(c, myargv);
        }
        else
        {
            //命令
            //fork + exec
            run_cmd(c, cmd, myargv);
        }
    }
    close(c);
}

int start_thread(int c)
{
    pthread_t id;
    pthread_create(&id, NULL, work_thread, (void*)c);
}